#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <Windows.h>
#include <time.h>
#include <complex>
#include <complex.h>
#include <math.h>
#include <stack>
#include "error_define.h"
#define VERSION "SimpleShell 1.5.1"
#define IN 0
#define OUT 1
using namespace std;
const int CMD = 0;
const int BASH = 1;
bool is_echo = true;
bool logMode = false;
short shellStyle = CMD;
string fileDir = "C:\\";
string SUBTITLE[] = {"...","Try now!","gitee.com/lizesheng520!","Try...Catch it!"};
stack<char> opt;
stack<double> val;
int LargeNumberFactorial[1000] = { 0 };

char opt_set[10] = "+-*/()=.";
int level(char theOpt);
bool in_set(char theChar);
bool del_space(string &theString);
string to_string(int theInt);
bool change(string &from, string &to);
bool compute(string &theExp);
int Judge_rectangle(string& theStr);
double cal_rect_number(string& theStr, int theInt);
bool Judge_factorial(string& theStr);
int cal_factorial(int theInt);
int cal_factorial_number(string& theStr);
void pass() {}
int level(char theOpt) {
	switch (theOpt) {
		case '(':
			return 0;
		case '+':
		case '-':
			return 1;
		case '*':
		case '/':
			return 2;
		case ')':
			return 3;
	}
}
bool in_set(char theChar) {
	for (int i = 0; i < 8; i++) {
		if (theChar == opt_set[i])
			return true;
	}
	return false;
}
bool del_space(string& theString) {
	string res;
	for (int i = 0; i < theString.length(); i++) {
		if (in_set(theString[i]) || isdigit(theString[i]))
			res += theString[i];
		else if (theString[i] == ' ') {}
		else {
			cout << "The enter is Wrong" << endl;
			return false;
		}
	}
	theString = res;
	return true;
}
string to_string(int theInt) {
	if (theInt == 0)
		return string("0");
	bool neg = false;
	if (theInt < 0) {
		neg = true;
		theInt = -theInt;
	}
	string res;
	while (theInt != 0) {
		char c = (theInt % 10) + '0';
		res = c + res;
		theInt /= 10;
	}
	if (neg)
		res = '-' + res;
	return res;
}
bool change(string &from, string &to) {
	int theInt = 0;
	int state = OUT;
	char c;

	for (int i = 0; i < from.length(); i++) {
		c = from[i];
		if (isdigit(c)) {
			to = to + c;
			theInt *= 10;
			theInt += c - '0';
			state = IN;
		} else {
			if (state == IN && c == '.') {
				to = to + '.';
				theInt = 0;
				continue;
			}
			if (state == IN && c != '.') {
				to +=  ' ';
				theInt = 0;
			}
			if (c == '=')
				break;
			else if (c == '(')
				opt.push(c);
			else if (c == ')') {
				while (!opt.empty() && opt.top() != '(') {
					to += opt.top();
					to += ' ';
					opt.pop();
				}
				opt.pop();
			} else {
				while (true) {
					if (opt.empty() || opt.top() == '(')
						opt.push(c);
					else if (level(c) > level(opt.top()))
						opt.push(c);
					else {
						to += opt.top();
						to += ' ';
						opt.pop();
						continue;
					}
					break;
				}
			}
			state = OUT;
		}
	}
	while (!opt.empty()) {
		to += opt.top();
		to += ' ';
		opt.pop();
	}
	return true;
}
bool compute(string &theExp) {
	int theInt = 0;
	int state = OUT;
	char c;
	bool dot = false;
	double count = 1.0;
	for (int i = 0; i < theExp.length(); i++) {
		c = theExp[i];
		if (isdigit(c)|| c == '.') {
			if (isdigit(c)) {
				theInt *= 10;
				theInt += c - '0';
				state = IN;
				if (dot == true)
					count *= 10.0;
			}
			if (c == '.') {
				dot = true;
				continue;
			}
		} else {
			dot = false;
			double ans = theInt / count;
			count = 1.0;
			if (state == IN) {
				val.push(ans);
				theInt = 0;
			}
			double x, y;
			if (c != ' ') {
				x = val.top();
				val.pop();
				y = val.top();
				val.pop();

				switch (c) {
					case '+':
						val.push(x + y);
						break;
					case '-':
						val.push(y - x);
						break;
					case '*':
						val.push(x * y);
						break;
					case '/':
						val.push(y / x);
						break;
					default:
						cout << "Unknow fail!" << endl;
				}
			}
			state = OUT;
		}
	}
	return true;
}
int Judge_rectangle(string& theStr) {
	string str1 = "sin";
	string str2 = "cos";
	string str3 = "tan";
	string str4 = "arcsin";
	string str5 = "arccos";
	string str6 = "arctan";
	const char* show1, *show2, *show3, *show4, *show5, *show6;
	show4 = strstr(theStr.c_str(), str4.c_str());
	if (show4 != NULL)
		return 4;
	show5 = strstr(theStr.c_str(), str5.c_str());
	if (show5 != NULL)
		return 5;
	show6 = strstr(theStr.c_str(), str6.c_str());
	if (show6 != NULL)
		return 6;
	show1 = strstr(theStr.c_str(), str1.c_str());
	if (show1 != NULL)
		return 1;
	show2 = strstr(theStr.c_str(), str2.c_str());
	if (show2 != NULL)
		return 2;
	show3 = strstr(theStr.c_str(), str3.c_str());
	if (show3 != NULL)
		return 3;
	return 0;
}
double cal_rect_number(string& theStr, int theInt) {
	int number = 0;
	for (int i = 0; theStr[i] != ')'; i++) {
		if (isdigit(theStr[i])) {
			number *= 10;
			number += theStr[i] - '0';
		}
	}
	double ans = 0.0;
	switch (theInt) {
		case 1:
			ans = sin(number);
			break;
		case 2:
			ans = cos(number);
			break;
		case 3:
			ans = tan(number);
			break;
		case 4:
			ans = asin(number);
			break;
		case 5:
			ans = acos(number);
			break;
		case 6:
			ans = atan(number);
			break;
		default:
			return 0;
			break;
	}
	return ans;
}
bool Judge_factorial(string& theStr) {
	int len = theStr.length();
	for (int i = 0; i < len; i++)
		if (theStr[i] == '!')
			return true;
	return false;
}
int cal_factorial_number(string& theStr) {
	int number = 0;
	for (int i = 0; theStr[i] != '!'; i++) {
		if (isdigit(theStr[i])) {
			number *= 10;
			number += theStr[i] - '0';
		}
	}
	return  number;
}
int cal_factorial(int theInt) {
	LargeNumberFactorial[0] = 1;
	LargeNumberFactorial[1] = 1;
	int digit = 1;
	for (int i = 1; i <= theInt; i++) {
		for (int j = 1; j <= digit; j++) {
			LargeNumberFactorial[j] *= i;
		}
		for (int j = 1; j <= digit; j++) {
			if (LargeNumberFactorial[j] > 10) {
				for (int k = 1; k <= digit; k++) {
					if (LargeNumberFactorial[digit] > 9)
						digit++;
					LargeNumberFactorial[k + 1] += LargeNumberFactorial[k] / 10;
					LargeNumberFactorial[k] %= 10;
				}
			}
		}
	}
	return digit;
}

void startElseEXE(std::string sdir) {                //string argc
	string scomm="start ";
	scomm+=sdir;
	const char * cccomm;
	cccomm = scomm.c_str();
	system(cccomm);
}

void listFiles(const char * dir) {
	HANDLE hFind;
	WIN32_FIND_DATA findData;
	LARGE_INTEGER size;
	char dirNew[100];
	strcpy(dirNew, dir);
	strcat(dirNew, "\\*.*");
	hFind = FindFirstFile(dirNew, &findData);
	do {
		if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0
		        && strcmp(findData.cFileName, ".") != 0
		        && strcmp(findData.cFileName, "..") != 0
		   ) {
			strcpy(dirNew, dir);
			strcat(dirNew, "\\");
			strcat(dirNew, findData.cFileName);
			listFiles(dirNew);
		} else {
			size.LowPart = findData.nFileSizeLow;
			size.HighPart = findData.nFileSizeHigh;
			cout << findData.cFileName << "\t" << size.QuadPart << " bytes\n";
		}
	} while (FindNextFile(hFind, &findData));
	FindClose(hFind);
}

int main(int argc, char** argv) {
	int subtitle;
	std::string titleCommand = "title Simple Shell:";
	time_t t;
	srand((unsigned) time(&t));
	subtitle = rand() % 5;
	titleCommand += SUBTITLE[subtitle];
	char PCName[255];
	char UserName[255];
	unsigned long size = 255;
	GetComputerName(PCName, &size);
	GetUserName(UserName, &size);
	system(titleCommand.c_str());
	cout << "Welcome to " << VERSION <<"!\n";
	cout << "This is RELEASE of this simple shell.\n";
	cout << "You can use this shell like cmd.But I will update this shell.\n";
	cout << endl;
	cout << "Use it for fun!\n";
	cout << "Thanks Embarcadero Dev C++!\n";
	cout << "Sponsor:No\n";
	while (true) {
		try {
			if(logMode == true) {
				cout << "Log mode using.\n";
			}
			string command = "";
			if(shellStyle == CMD) {
				pass();
			}
			if(shellStyle == BASH) {
				printf("%s",UserName);
				cout << "@";
				printf("%s",PCName);
				cout<<" ";
			}
			if(is_echo) {
				cout << fileDir << " ";

			}
			cin >> command;
			for(int i=0; i<command.size(); i++) {
				if(command[i] >= 'A' && command[i] <= 'Z') {
					command[i]+=32;
				}
			}
			if(command == "help") {
				cout << "Command List:\nSET Change setting.\nCD Change the dir\nLF List all files in this dir\nHELP Show this info\nEXIT Exit the shell\nCLEAR/CLEAN/CLS Clear screen\nECHO Just show it!\nSTART Start a program.\nRM Remove a file.\nCF Create a new file.\nCAL Like name.\n";
			} else if(command == "cd") {
				string dir;
				cin.get();
				getline(cin,dir);
				if(dir == "back") {
					continue;
				}
				if(dir[1] == ':' && dir[2] == '\\') {
					fileDir = dir;
					continue;
				}
				fileDir += dir;
				fileDir += "\\";
				if(logMode == true) {
					cout << "Command over.\n";
				}
			} else if(command == "lf") {
				char dir[16384];
				cin.get();
				cout << "Input '\\' to use this dir(beta)\n";
				cout << "Enter a directory (do not add \'\\\' in the end): ";
				cin.getline(dir, 16383);
				if(dir == "back") {
					continue;
				}
				if(dir[0] = '\\') {
					for(int i=0; i<fileDir.size(); i++) {
						dir[i]=fileDir[i];
					}
				}
				listFiles(dir);
				if(logMode == true) {
					cout << "Command over.\n";
				}
			} else if(command == "start") {
				string dir;
				cin.get();
				getline(cin,dir);
				if(dir == "back") {
					continue;
				}
				startElseEXE(dir);
				if(logMode == true) {
					cout << "Command over.\n";
				}
			} else if(command == "echo") {
				string echoText;
				cin>>echoText;
				cout<<echoText<<endl;
			} else if(command == "clear" || command == "clean" || command == "cls") {
				system("cls");
				if(logMode == true) {
					cout << "Command over.\n";
				}
			} else if(command == "rm" || command == "del") {
				string fileToDel,temp2;
				cout << "Input file you want to delete:";
				cin >> fileToDel;
				temp2 = fileDir;
				fileDir += fileToDel;
				const char * temp;
				temp = fileDir.c_str();
				BOOL fileRemoved = DeleteFile(temp);
				if(fileRemoved) {
					cout << "Delete File: " << temp << endl;
				}
				fileDir = temp2;
				if(logMode == true) {
					cout << "Command over.\n";
				}
			} else if(command == "exit") {
				if(logMode == true) {
					cout << "Exit Program.\n";
					system("pause");
				}
				return 0;
			} else if(command == "cf") {
				string createFileName;
				cout<<"Input the name of new file:";
				cin>>createFileName;
				if(CreateFile(createFileName.c_str(),(GENERIC_READ | GENERIC_WRITE),(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL) == INVALID_HANDLE_VALUE) {
					cout<<"File create fail.\n";
					throw FileException;
				}
			} else if(command == "set") {
				string setOption;
				cout << "Settings:\n(S)tyle:Change style;\n(L)og Mode:Open/Close log mode.\n";
				cin >> setOption;
				for(int i=0; i<setOption.size(); i++) {
					if(setOption[i] >= 'A' && setOption[i] <= 'Z') {
						setOption[i]+=32;
					}
				}
				if(setOption == "s") {
					string style;
					cout << "What sytle?((B)ash,(C)MD),(N)o Dir:\n";
					cin >> style;
					for(int i=0; i<style.size(); i++) {
						if(style[i] >= 'A' && style[i] <= 'Z') {
							style[i]+=32;
						}
					}
					if(style == "b") {
						shellStyle = BASH;
					} else if(style == "c") {
						shellStyle = CMD;
					} else if(style == "n") {
						is_echo = !is_echo;
					} else {
						throw NotFoundException;
					}
				} else if(setOption == "l") {
					logMode = !logMode;
					if(logMode) {
						cout << "LOG MODE:open\nDEBUG START.\n";
					} else {
						cout << "LOG MODE:off\nDEBUG END.\n";
					}
				} else {
					cout << "set :" << setOption << " setting option not found.\n";
				}
				cout << "Setting change:over.\n";
			} else if(command == "error_test") {
				int errorCode;
				cin>>errorCode;
				throw errorCode;
			} else if(command == "oct") {				//NO HEX INPUT!
				int octNum;
				cin>>octNum;
				cout << showbase << oct << octNum << endl;
			} else if(command == "hex") {
				int hexNum;
				cin>>hexNum;
				cout << showbase << hex << hexNum << endl;
			} else if(command == "cal") {
				string init_exp;
				cin >> init_exp;
				if(Judge_rectangle(init_exp) != 0) {
					double res = 0;
					res = cal_rect_number(init_exp, Judge_rectangle(init_exp));
					if (res)
						cout  << fixed << setprecision(9) << res << endl;
				}
				else if (Judge_factorial(init_exp)) {
					int n = cal_factorial_number(init_exp);
					for (int i = cal_factorial(n); i > 0; i--)
						cout << LargeNumberFactorial[i];
					cout << endl;
				}
				else {
					cin.clear();
					cin.sync();
					while (!opt.empty()) {
						opt.pop();
					}
					while (!val.empty()) {
						val.pop();
					}
					del_space(init_exp);
					string cng_exp;
					cng_exp.clear();
					change(init_exp, cng_exp);
					compute(cng_exp);
					double stdans = val.top();
					cout << (double)stdans << endl;
				}
			} else if(command=="restart") {
				TCHAR szPath[MAX_PATH];
   			    GetModuleFileName(NULL, szPath, MAX_PATH); 
     		    STARTUPINFO StartInfo;
       			PROCESS_INFORMATION procStruct;
        		memset(&StartInfo, 0, sizeof(STARTUPINFO));
        		StartInfo.cb = sizeof(STARTUPINFO);
				CreateProcess((LPCTSTR)szPath,NULL,NULL,NULL,FALSE,HIGH_PRIORITY_CLASS,NULL,NULL,&StartInfo,&procStruct);
            	throw ProgramRestart;
			} else {
				cout << "Command '" << command << "' Not Found.\n";
			}
			if(logMode == true) {
				cout << "A while over.\n";
			}
		} catch (int errorMsg) {
			if(errorMsg == FileException) {
				cerr<<"Create file fail.Did you give this program Administrator power?\n";
			} else if(errorMsg == NotFoundException) {
				cerr<<"Input not found.\n";
			} else if(errorMsg == ProgramRestart) {
				system("cls");
				cerr<<"RESTARTING THE SHELL\n";
				Sleep(5000);
				return 0;
			}
		}
	}
}
